package com.zytech.zspider.task.impl.http;

import com.zytech.modules.lang.Langs;
import com.zytech.modules.lang.Strings;
import com.zytech.zspider.core.TaskDefinition;
import com.zytech.zspider.core.TaskDefinitionHelper;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts;
import org.apache.http.HttpHost;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.*;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.*;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.nio.charset.CodingErrorAction;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;

/**
 * @author siuming
 */
final class HttpClientFactory {

    static final CookieStore COOKIE_STORE = new BasicCookieStore();
    static final PoolingHttpClientConnectionManager CONNECTION_MANAGER = ofConnectionManager();

    /**
     * @param settings
     * @return
     */
    static HttpClient of(Map<String, String> settings) {
        try {

            if (null != HttpClientHolder.get()) {
                return HttpClientHolder.get();
            }

            HttpClientBuilder clientBuilder = HttpClients
                    .custom()
                    .setConnectionManager(CONNECTION_MANAGER);

            String[] vals = Strings.split(TaskDefinitionHelper.getSetting(settings, TaskDefinition.Settings.HTTP_PROXY, ""), ":");
            if (vals.length == 3) {
                HttpHost proxyHost = new HttpHost(vals[1], Integer.parseInt(vals[2]), vals[0]);
                clientBuilder.setProxy(proxyHost);
            }

            int retries = Integer.valueOf(TaskDefinitionHelper.getSetting(
                            settings,
                            TaskDefinition.Settings.HTTP_RETRIES,
                            "3")
            );
            clientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(retries, false));

            final boolean redirect = Boolean.valueOf(TaskDefinitionHelper.getSetting(
                            settings,
                            TaskDefinition.Settings.HTTP_FOLLOW_REDIRECT,
                            "true")
            );
            final String redirectMethods = TaskDefinitionHelper.getSetting(
                    settings,
                    TaskDefinition.Settings.HTTP_FOLLOW_REDIRECT_METHODS,
                    "GET,HEAD,POST"
            );
            clientBuilder.setRedirectStrategy(new DefaultRedirectStrategy() {
                @Override
                protected boolean isRedirectable(String method) {
                    return redirect
                            && StringUtils.contains(redirectMethods, method);
                }
            });

            RequestConfig requestConfig = HttpClientFactory.ofRequestConfig(settings);
            clientBuilder.setDefaultRequestConfig(requestConfig);

            HttpClient httpClient = clientBuilder.setDefaultCookieStore(COOKIE_STORE).build();
            HttpClientHolder.set(httpClient);
            return httpClient;
        } catch (Exception e) {
            throw Langs.toUncheck(e);
        }
    }

    private static RequestConfig ofRequestConfig(Map<String, String> props) {
        int timeout = Integer.valueOf(TaskDefinitionHelper.getSetting(props, TaskDefinition.Settings.HTTP_TIMEOUT, "15000"));
        return RequestConfig
                .custom()
                .setCookieSpec(CookieSpecs.BROWSER_COMPATIBILITY)
                .setSocketTimeout(timeout)
                .setConnectTimeout(timeout)
                .setConnectionRequestTimeout(timeout)
                .setStaleConnectionCheckEnabled(true)
                .build();
    }

    private static SSLContext ofSSLContext() {
        try {
            TrustManager x509 = new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                }

                @Override
                public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                }

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            };

            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, new TrustManager[]{x509}, null);
            return sslContext;
        } catch (Exception e) {
            throw new Error(e);
        }
    }

    private static PoolingHttpClientConnectionManager ofConnectionManager() {
        Registry<ConnectionSocketFactory> registry = RegistryBuilder
                .<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", new SSLConnectionSocketFactory(ofSSLContext()))
                .build();

        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
        SocketConfig socketConfig = SocketConfig.custom().setTcpNoDelay(true).build();

        MessageConstraints messageConstraints = MessageConstraints
                .custom()
                .setMaxHeaderCount(200)
                .setMaxLineLength(2000)
                .build();

        ConnectionConfig connectionConfig = ConnectionConfig
                .custom()
                .setMalformedInputAction(CodingErrorAction.IGNORE)
                .setUnmappableInputAction(CodingErrorAction.IGNORE)
                .setCharset(Consts.UTF_8)
                .setMessageConstraints(messageConstraints)
                .build();

        connectionManager.setMaxTotal(200);
        connectionManager.setDefaultMaxPerRoute(50);
        connectionManager.setDefaultSocketConfig(socketConfig);
        connectionManager.setDefaultConnectionConfig(connectionConfig);
        return connectionManager;
    }

    private HttpClientFactory() {
    }
}
